using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.ComponentModel;
using System.Windows.Threading;
using System.Threading;
using System.Data.SQLite;
using System.Data;
using System.Globalization;
using ResourceBlender.DAL;
using ResourceBlender.Core;

namespace ResourceBlender.Express {
    /// <summary>
    /// Allows translation databases (.rbt files) to be edited.
    /// </summary>
    public partial class WinEditRBT : Window {
        private BackgroundWorker worker;
        private DispatcherTimer timer;
        private int autoSaveInterval;
        private DateTime lastSave;

        private ObservableSortedDictionary<string, CultureInfo> languages; // <code, info>
        private ObservableCollection<ObservableResource> resources;
        private string fileName;
        private DataTemplate ascSorted,
                             descSorted;

        private SQLiteConnection conn;

        /// <summary>
        /// Initializes a new <see cref="WinEditRBT"/> to edit the specified file.
        /// </summary>
        /// <param name="fileName">The path of the file to open.</param>
        public WinEditRBT(string fileName) {
            InitializeComponent();
            ascSorted = (DataTemplate)FindResource("HeaderTemplateAsc");
            descSorted = (DataTemplate)FindResource("HeaderTemplateDesc");
            this.fileName = fileName;
            this.autoSaveInterval = ResourceBlender.Express.Properties.Settings.Default.AutoSaveInterval;
            timer = new DispatcherTimer(TimeSpan.FromMilliseconds(autoSaveInterval), DispatcherPriority.Background, new EventHandler(DoAutoSave), this.Dispatcher);
        }

        /// <summary>
        /// Loads the file specified in the constructor when the window loads.
        /// Finds all unique languages and creates a DataTable of resources
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            worker = new BackgroundWorker();
            languages = new ObservableSortedDictionary<string, CultureInfo>(new Comparers.CultureInfoEnglshNameComparer());
            resources = new ObservableCollection<ObservableResource>();
            worker.DoWork += (s, dwe) => {
                ObservableCollection<ObservableResource> resourcesTemp = new ObservableCollection<ObservableResource>();
                try {
                    if(conn == null || conn.State != System.Data.ConnectionState.Open) {
                        conn = new SQLiteConnection(Database.GetSQLiteConnectionString(fileName));
                        conn.Open();
                    }
                } catch(Exception ex) {
                    throw new Exception(string.Format("Error opening file: {0}", ex.Message));
                }
                try {
                    using(SQLiteCommand cmd = conn.CreateCommand()) {
                        cmd.CommandText = "SELECT DISTINCT language FROM languages";
                        using(SQLiteDataReader reader = cmd.ExecuteReader()) {
                            while(reader.Read()) {
                                string code = reader.GetString(0).Trim();
                                if(code.Length > 0) {
                                    languages.Add(code, Utils.GetCultureInfo(code));
                                }
                            }
                            reader.Close();
                        }
                        if(languages.Count == 0) {
                            throw new Exception("This file does not contain any languages");
                        }
                        cmd.CommandText = "SELECT r.resourceid, c.comment FROM resources AS r LEFT JOIN resourcecomments AS c ON r.resourceid = c.resourceid GROUP BY r.resourceid";
                        using(SQLiteDataReader reader = cmd.ExecuteReader()) {
                            while(reader.Read()) {
                                ObservableResource resource = new ObservableResource(int.Parse(reader[0].ToString()), reader[1].ToString());
                                resourcesTemp.Add(resource);
                            }
                            reader.Close();
                        }
                        cmd.CommandText = "SELECT language, translation, translatorcomment FROM resources WHERE resourceid = @rID";
                        cmd.Parameters.AddWithValue("@rID", "");
                        int numLanguages = languages.Count;
                        string[] langCodes = languages.Keys.ToArray();

                        foreach(ObservableResource res in resourcesTemp) {
                            cmd.Parameters[0].Value = res.ID;
                            using(SQLiteDataReader reader = cmd.ExecuteReader()) {
                                while(reader.Read()) {
                                    string code = reader[0].ToString();
                                    res.Translations.Add(code, new RBTranslation(code, reader[1].ToString(), reader[2].ToString()));
                                }
                                reader.Close();
                                if(res.Translations.Count < numLanguages) {
                                    foreach(string code in langCodes) {
                                        if(!res.Translations.ContainsKey(code)) {
                                            res.Translations.Add(code, new RBTranslation(code, null, null));
                                        }
                                    }
                                }
                            }
                        }
                    }
                } catch {
                    throw;
                } finally {
                    if(conn != null) {
                        try {
                            conn.Close();
                        } catch { }
                    }
                }
                dwe.Result = resourcesTemp;
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    lblDb.Text = "Error";
                    DialogBox.ShowAlert(this, rwe.Error.Message, "Error");
                    Close();
                } else {
                    lblDb.Text = fileName; // wait until the file's been loaded
                    if(rwe.Result != null) {
                        resources = rwe.Result as ObservableCollection<ObservableResource>;
                    }
                    lstGrid.ItemsSource = resources;
                    int tabIndex = 1,
                        shortcutKey = 1;
                    Thickness inputPadding = new Thickness(0, 2, 0, 2);
                    int colWidth = (int)((lstGrid.ActualWidth - grdResources.Columns[0].ActualWidth) / languages.Count) - 20;
                    if(colWidth < 120) {
                        colWidth = 120;
                    }
                    foreach(KeyValuePair<string, CultureInfo> language in languages) {
                        GridViewColumn col = new GridViewColumn();
                        col.Header = language.Value.EnglishName;
                        col.DisplayMemberBinding = new Binding(string.Format("Translations[{0}].Translation",language.Key));
                        col.Width = colWidth;
                        grdResources.Columns.Add(col);
                        DatabaseTranslationInput translationInput = new DatabaseTranslationInput(language.Key, languages);
                        translationInput.TabIndex = tabIndex;
                        translationInput.IsTabStop = false;
                        if(shortcutKey < 10) {
                            KeyBinding bndShortcut = new KeyBinding(NavigationCommands.GoToPage, new KeyGesture((Key)Enum.Parse(typeof(Key), "D" + shortcutKey), ModifierKeys.Control, "Ctrl+" + shortcutKey));
                            bndShortcut.CommandParameter = language.Key;
                            this.InputBindings.Add(bndShortcut);
                            shortcutKey++;
                        }
                        tabIndex = translationInput.TabIndex +1;
                        translationInput.Margin = inputPadding;
                        Binding bndTranslation = new Binding(string.Format("SelectedItem.Translations[{0}]", language.Key));
                        bndTranslation.Mode = BindingMode.TwoWay;
                        bndTranslation.Source = lstGrid;
                        bndTranslation.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
                        translationInput.SetBinding(DatabaseTranslationInput.TranslationProperty, bndTranslation);
                        stkTranslations.Children.Add(translationInput);
                    }
                    if(lstGrid.Items.Count > 0) {
                        lstGrid.SelectedIndex = 0;
                    }
                    lastSave = DateTime.Now;
                    if(itmAutoSave.IsChecked) {
                        timer.IsEnabled = true;
                        timer.Start();
                    }
                }
                SetStatus(null, false);
            };
            SetStatus("Loading resources...", true);
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Shows the selected resource and finds elements which use it when the selected resource changes.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void lstGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate() { }));
                }
            }
            worker = new BackgroundWorker();
            string selectedId = (lstGrid.SelectedValue ?? string.Empty).ToString();
            if(selectedId.Length == 0) {
                return;
            }
            worker.DoWork += (s, dwe) => {
                if(conn == null || conn.State != System.Data.ConnectionState.Open) {
                    conn = new SQLiteConnection(Database.GetSQLiteConnectionString(fileName));
                    conn.Open();
                }
                try {
                    using(SQLiteCommand cmd = conn.CreateCommand()) {
                        cmd.CommandText = "SELECT applicationname AS app, bundle, elementname AS element FROM elements WHERE resourceid = @resourceId ORDER BY applicationname, bundle, elementname";
                        cmd.Parameters.AddWithValue("@resourceId", selectedId);
                        using(SQLiteDataReader reader = cmd.ExecuteReader()) {
                            string app,
                                   bundle,
                                   element,
                                   lastApp = string.Empty,
                                   lastBundle = string.Empty,
                                   lastElement = string.Empty;
                            Dictionary<string, List<string>> applications = new Dictionary<string, List<string>>();
                            while(reader.Read()) {
                                app = reader["app"].ToString();
                                bundle = reader["bundle"].ToString();
                                element = reader["element"].ToString();
                                if(app != lastApp) {
                                    lastApp = app;
                                    applications.Add(app, new List<string>());
                                }
                                applications[app].Add(string.Format("{0} - {1}", bundle, element));
                            }
                            dwe.Result = applications;
                        }
                    }
                } catch {
                    throw;
                } finally {
                    if(conn != null) {
                        try {
                            conn.Close();
                        } catch { }
                    }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                lstGrid.IsEnabled = true;
                SetStatus(null, false);
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, string.Format("Error loading element information: {0}", rwe.Error.Message), "Error");
                } else {
                    Dictionary<string, List<string>> applications = rwe.Result as Dictionary<string, List<string>>;
                    if(applications.Count > 0) {
                        treElement.Items.Clear();
                        int numElements = 0;
                        foreach(KeyValuePair<string, List<string>> app in applications) {
                            TreeViewItem itmApp = new TreeViewItem();
                            itmApp.Header = app.Key;
                            itmApp.FontWeight = FontWeights.Bold;
                            treElement.Items.Add(itmApp);
                            foreach(string element in app.Value) {
                                TreeViewItem itmElement = new TreeViewItem();
                                itmElement.Header = string.Format(element);
                                itmApp.FontWeight = FontWeights.Normal;
                                itmApp.Items.Add(itmElement);
                                numElements++;
                            }
                            itmApp.IsExpanded = true;
                        }
                        treElement.IsExpanded = true;
                        treElement.Header = string.Format("{0} elements", numElements);
                    } else {
                        treElement.Header = "No elements use this resource";
                    }
                }
            };
            lstGrid.IsEnabled = false;
            SetStatus("Loading translations...", true);
            worker.RunWorkerAsync();
            string comment = (lstGrid.SelectedItem as ObservableResource).Comment;
            txtComment.Text = comment.Length > 0 ? comment : "(None)";
        }

        /// <summary>
        /// Sorts the grid.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void OnColumnHeaderClick(object sender, RoutedEventArgs e) {
            // too slow to work
            /*if(e.OriginalSource is GridViewColumnHeader) { // sort the grid
                GridViewColumn column = ((GridViewColumnHeader)e.OriginalSource).Column;
                string sortPath = column.DisplayMemberBinding == null ? (column.CellTemplate.DataType ?? string.Empty).ToString() : (column.DisplayMemberBinding as Binding).Path.Path;
                if(sortPath.Length == 0) {
                    return; // no field specified
                }
                if(sortPath.StartsWith("Translation")) { // a binding inside the translation array - eg: Translations[en].Value or Translations[de-DE].Value
                    //int startBracket = sortPath.IndexOf('[')+1;
                    //sortPath = sortPath.Substring(
                }
                sortPath = "Translations['en-GB'].Value";
                SortDescription sd = new SortDescription();
                ICollectionView dataView = CollectionViewSource.GetDefaultView(resources);
                dataView.SortDescriptions.Clear();
                if(column.HeaderTemplate == ascSorted) {
                    column.HeaderTemplate = descSorted;
        //            gridSort = sortPath + " DESC"; ;
                    sd = new SortDescription(sortPath, ListSortDirection.Descending);
                } else if(column.HeaderTemplate == descSorted) {
                    column.HeaderTemplate = null;
      //              gridSort = "";
                } else {
                    column.HeaderTemplate = ascSorted;
//                    gridSort = sortPath + " ASC";
                    sd = new SortDescription(sortPath, ListSortDirection.Ascending);
                }
                if(sd.PropertyName.Length > 0) {
                    dataView.SortDescriptions.Add(sd);
                }
                dataView.Refresh();
            }*/
        }

        /// <summary>
        /// Toggles AutoSave on and off.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void ToggleAutoSave(object sender, RoutedEventArgs e) {
            if(itmAutoSave.IsChecked) {
                timer.Start();
                timer.IsEnabled = true;
            } else {
                timer.Stop();
                timer.IsEnabled = false;
            }
            ResourceBlender.Express.Properties.Settings.Default.Save();
        }

        /// <summary>
        /// Determines whether the Save command can execute.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanExecuteSave(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = this.IsLoaded && (worker == null || !worker.IsBusy);
        }

        /// <summary>
        /// Performs an auto-save
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void DoAutoSave(object sender, EventArgs e) {
            if((worker == null || (worker != null && !worker.IsBusy)) && DateTime.Now.Subtract(lastSave).TotalMilliseconds >= autoSaveInterval) {
                ApplicationCommands.Save.Execute(null, this);
            }
        }

        /// <summary>
        /// Saves the list of resources back to the .rbt file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Save(object sender, ExecutedRoutedEventArgs e) {
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate() { }));
                }
            }
            worker = new BackgroundWorker();
            worker.WorkerReportsProgress = true;
            worker.ProgressChanged += (s, pce) => { // if the worker finishes early
                SetStatus(pce.UserState.ToString(), false);
            };
            worker.DoWork += (s, dwe) => {
                DateTime dtStart = DateTime.Now;
                if(conn == null || conn.State != System.Data.ConnectionState.Open) {
                    conn = new SQLiteConnection(Database.GetSQLiteConnectionString(fileName));
                    conn.Open();
                }
                try {
                    using(SQLiteTransaction trans = conn.BeginTransaction()) {
                        using(SQLiteCommand cmdResource = conn.CreateCommand()) {
                            cmdResource.CommandText = "DELETE FROM resources";
                            cmdResource.ExecuteNonQuery();
                            cmdResource.CommandText = "INSERT INTO resources (resourceid, language, translation, translatorcomment) VALUES (@rID, @language, @translation, @translatorcomment)";
                            cmdResource.Parameters.AddWithValue("@rID", "");
                            cmdResource.Parameters.AddWithValue("@language", "");
                            cmdResource.Parameters.AddWithValue("@translation", "");
                            cmdResource.Parameters.AddWithValue("@translatorcomment", "");
                            ObservableCollection<ObservableResource> resourcesTemp = dwe.Argument as ObservableCollection<ObservableResource>;
                            foreach(ObservableResource res in resourcesTemp) {
                                cmdResource.Parameters[0].Value = res.ID;
                                foreach(KeyValuePair<string, RBTranslation> translation in res.Translations) {
                                    cmdResource.Parameters[1].Value = translation.Value.LanguageCode;
                                    cmdResource.Parameters[2].Value = translation.Value.Translation ?? string.Empty;
                                    // when re-importing, if the translation is empty the translation is deleted from the database
                                    if(translation.Value.Comment != null && translation.Value.Comment.Length > 0) {
                                        cmdResource.Parameters[3].Value = translation.Value.Comment;
                                    } else {
                                        cmdResource.Parameters[3].Value = DBNull.Value;
                                    }
                                    cmdResource.ExecuteNonQuery();
                                }
                            }
                        }
                        trans.Commit();
                    }
                } catch {
                    throw;
                } finally {
                    if(conn != null) {
                        try {
                            conn.Close();
                        } catch { }
                    }
                }
                if((DateTime.Now - dtStart).TotalSeconds < 4) { // give the user chance to see the message
                    worker.ReportProgress(0, "Save Complete");
                    Thread.Sleep(200);
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(null, false);
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, "Error: Could not connect to database - " + rwe.Result.ToString(), "Database Error");
                }
                lastSave = DateTime.Now;
            };
            SetStatus("Saving...", true);
            worker.RunWorkerAsync(resources);
        }

        #region Element Navigation (Next/Previous)
        /// <summary>
        /// Retrieves the next/previous element from the parent window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void MoveElement(object sender, ExecutedRoutedEventArgs e) {
            if(e.Command == NavigationCommands.NextPage) {
                lstGrid.SelectedIndex++;
            } else if(e.Command == NavigationCommands.PreviousPage) {
                lstGrid.SelectedIndex--;
            }
            lstGrid.ScrollIntoView(lstGrid.SelectedItem);
        }

        /// <summary>
        /// Determines whether there is an element in the grid before this one, which can be edited.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanMovePrevious(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = (worker == null || !worker.IsBusy) && lstGrid.SelectedIndex > 0;
        }

        /// <summary>
        /// Determines whether there is an element in the grid after this one, which can be edited.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanMoveNext(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = (worker == null || !worker.IsBusy) && lstGrid.SelectedIndex < resources.Count - 1;
        }
        #endregion

        /// <summary>
        /// Moves to the translation input at the index of the pressed key, changes the keyboard focus and move the caret to the end of the text.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void GoToTranslation(object sender, ExecutedRoutedEventArgs e) {
            if(e.Parameter == null) {
                lstGrid.Focus();
            } else {
                foreach(UIElement child in stkTranslations.Children) {
                    if(child is DatabaseTranslationInput) {
                        DatabaseTranslationInput inp = (child as DatabaseTranslationInput);
                        if(inp.LanguageCode == e.Parameter.ToString()) {
                            inp.FocusTranslation();
                            break;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Automatically translates using Google Translate. The CommandParameter should be an array containing the source CultureInfo and destination CultureInfo
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AutoTranslate(object sender, ExecutedRoutedEventArgs e) {
            CultureInfo[] langPair = (CultureInfo[])e.Parameter; // .NET culture codes
            string sourceCode = langPair[0].Name,
                   googleSourceCode = GoogleTranslator.DotNetCode2GoogleCode(sourceCode),
                   destinationCode = langPair[1].Name,
                   googleDestinationCode = GoogleTranslator.DotNetCode2GoogleCode(destinationCode);
            ObservableResource currentResource = (lstGrid.SelectedItem as ObservableResource);
            if(!currentResource.Translations.ContainsKey(sourceCode)) {
                DialogBox.ShowAlert(this, string.Format("The current resource does not contain a {0} translation", langPair[0].EnglishName), "Error");
                return;
            }
            string textToTranslate = currentResource.Translations[sourceCode].Translation.Trim();
            if(googleSourceCode.Length == 0 || googleDestinationCode.Length == 0) {
                DialogBox.ShowAlert(this, "Translation between the specified languages is not supported", "Not Supported");
            } else if(textToTranslate.Length == 0) {
                return;
            } else if(textToTranslate.Length > 4800) {
                DialogBox.ShowAlert(this, "Only strings up to 4800 characters in length can be translated", "Not Supported");
            } else {
                UserControl containingInput = WPFUtils.GetParentOfType(e.OriginalSource as DependencyObject, typeof(DatabaseTranslationInput)) as UserControl;
                BackgroundWorker translateWorker = new BackgroundWorker();
                translateWorker.DoWork += (s, dwe) => {
                    try {
                        dwe.Result = GoogleTranslator.Translate(textToTranslate, googleSourceCode, googleDestinationCode);
                    } catch(Exception ex) {
                        dwe.Result = ex.Message;
                    }
                };
                translateWorker.RunWorkerCompleted += (s, rwe) => {
                    containingInput.IsEnabled = true;
                    SetStatus(null, false);
                    if(rwe.Result is GoogleTranslator.TranslationResponse) {
                        GoogleTranslator.TranslationResponse response = rwe.Result as GoogleTranslator.TranslationResponse;
                        if(response.Status == 200) {
                            if(currentResource.Translations.ContainsKey(destinationCode)) {
                                currentResource.Translations[destinationCode].Translation = response.Data.TranslatedText;
                            } else {
                                currentResource.Translations.Add(destinationCode, new RBTranslation(destinationCode, response.Data.TranslatedText, ""));
                            }
                        } else {
                            DialogBox.ShowAlert(this,
                                string.Format("Error {0}: {1}",
                                    response.Status,
                                    response.Status == 400 ? string.Format("{0} translation not supported by {1}\n\n{2}", langPair[0].EnglishName, "Google", response.Details) : response.Details),
                                "Error translating resource");
                        }
                    } else {
                        DialogBox.ShowAlert(this, rwe.Result.ToString(), "Error translating resource");
                    }
                };
                containingInput.IsEnabled = false;
                SetStatus("Translating...", true);
                translateWorker.RunWorkerAsync();
            }
        }

        /// <summary>
        /// Shows the larger editor.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowFullEditor(object sender, ExecutedRoutedEventArgs e) {
            TextBox source = e.OriginalSource as TextBox;
            if(source == null) {
                return;
            }
            WinFullEditor editor = new WinFullEditor("Editor", source.Text);
            editor.Owner = this;
            editor.ShowDialog();
            if(editor.DialogResult == true) {
                source.Text = editor.Text;
            }
        }

        /// <summary>
        /// Opens the help file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowHelp(object sender, ExecutedRoutedEventArgs e) {
            WPFUtils.OpenHelp("Editing.RBT.Files.html");
        }

        /// <summary>
        /// Shows a DialogBox containing shortcut mappings.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowShortcuts(object sender, ExecutedRoutedEventArgs e) {
            FlowDocument doc = new FlowDocument();
            List<KeyValuePair<string, string>> shortcuts = new List<KeyValuePair<string, string>>() {
                {new KeyValuePair<string,string>("Shortcut Key","Action")},
                {new KeyValuePair<string,string>("Alt + Up","Previous resource")},
                {new KeyValuePair<string,string>("Alt + Down","Next resource")},
                {new KeyValuePair<string,string>("Ctrl + R","Focus the resource list")},
                {new KeyValuePair<string,string>("Ctrl + S","Save file")}
                //{new KeyValuePair<string,string>("Ctrl + 1","Jump to default language")}
            };
            int shortcutIndex = 1;
            foreach(KeyValuePair<string, CultureInfo> lang in languages) {
                if(shortcutIndex >= 10) {
                    break;
                }
                shortcuts.Add(new KeyValuePair<string, string>("Ctrl + " + shortcutIndex, string.Format("Jump to {0} translation", lang.Value.EnglishName)));
                shortcutIndex++;
            }
            doc.Blocks.Add(WPFUtils.GenerateFlowTable(shortcuts));
            doc.IsEnabled = false;
            DialogBox.Show(this, doc, "Shortcut Keys", false, DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Information, DialogBox.DialogBoxButton.OK);
        }

        /// <summary>
        /// Closes the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            Close();
        }

        /// <summary>
        /// Sets the text on the status message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="showProgress"><c>true</c> if the progress bar should be shown, otherwise <c>false</c>.</param>
        private void SetStatus(string message, bool showProgress) {
            prgStatus.Visibility = showProgress ? Visibility.Visible : Visibility.Collapsed;
            if(message == null) {
                int numResources = resources.Count;
                lblStatus.Text = string.Format("{0} {1}", numResources, numResources == 1 ? "resource" : "resources");
            } else {
                lblStatus.Text = message;
            }
        }
    }
}
